package com.tajlip;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

/**
 * Helper code that will parse files and parameters from HTTP POST multi-part
 * requests. Normally we'd use an existing helper API; however, Google doesn't
 * allow local file handles to be made. i.e. most every existing file upload API
 * freaks out because it can't make a temporary file to buffer uploaded data.
 *
 * This keeps everything in memory. Huzzah!
 * 
 * @author Jayson Falkner - jay@singleorganism.com
 */
public class FileUploadUtil {

    public static final Map<String /* name */, FileUpload> readUpload(InputStream is) throws IOException {
        // buffer everything in-memory
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        for (int bytesRead = is.read(buffer); bytesRead != -1; bytesRead = is.read(buffer)) {
            baos.write(buffer, 0, bytesRead);
        }
        // convert to an array
        final byte[] body = baos.toByteArray();
        // return the normally parsed file
        return readUpload(body);
    }

    public static final Map<String /* name */, FileUpload> readUpload(byte[] body) throws IOException {
        // first read the delimiter
        int delimiterOffset = 0;
        for (int i = 3; i < body.length; i++) {
            if (body[i - 1] == '\r' &&
                    body[i - 0] == '\n') {
                delimiterOffset = i + 1;
                break;
            }
        }
        // skip if no data is being uploaded -- sanity check
        if (delimiterOffset >= body.length || delimiterOffset -2 <= 0) {
            return new HashMap();
        }

        byte[] delimiterBytes = new byte[delimiterOffset - 2];
        System.arraycopy(body, 0, delimiterBytes, 0, delimiterBytes.length);

        // parse all the uploaded files
        Map<String /* name */, FileUpload> fileUploads = new HashMap(10);
        FileUpload fileUpload = parseFileUpload(delimiterBytes, body, delimiterOffset);
        while (fileUpload != null) {
            fileUploads.put(fileUpload.name, fileUpload);
            fileUpload = parseFileUpload(delimiterBytes, body, fileUpload.delimiterOffset);
        }

        return fileUploads;
    }

    private static final FileUpload parseFileUpload(byte[] delimiterBytes, byte[] body, int startingOffset) throws IOException {
        // check if another file could possibly be included
        if (body.length - startingOffset < delimiterBytes.length) {
            return null;
        }

        // offsets of the header section
        int headerSectionOffset = startingOffset;
        for (int i = startingOffset + 3; i < body.length; i++) {
            if (body[i - 3] == '\r' &&
                    body[i - 2] == '\n' &&
                    body[i - 1] == '\r' &&
                    body[i - 0] == '\n') {
                headerSectionOffset = i + 1;
                break;
            }
        }
        // parse the header as strings
        String header = new String(body, startingOffset, headerSectionOffset - startingOffset);
        StringReader sr = new StringReader(header);
        BufferedReader br = new BufferedReader(sr);

        FileUpload fileUpload = new FileUpload();
        for (String line = br.readLine(); line != null; line = br.readLine()) {
            if (line.startsWith("Content-Disposition:")) {
                String[] parts = line.split(";");
                for (int partIndex = 0; partIndex < parts.length; partIndex++) {
                    String part = parts[partIndex].trim();
                    String[] nameAndValue = part.split("=\"|\"");
                    if (nameAndValue.length < 2) {
                        continue;
                    }
                    String name = nameAndValue[0];
                    String value = nameAndValue[1];
                    // set the name
                    if (name.equals("name")) {
                        fileUpload.name = value;
                    }
                    // set the name
                    if (name.equals("filename")) {
                        fileUpload.filename = value;
                    }
                }
            }
        }

        // find the delimiter
        fileUpload.delimiterOffset = headerSectionOffset;
        for (int i = headerSectionOffset; i < body.length - delimiterBytes.length; i++) {
            boolean matchedDelimiter = true;
            for (int j = 0; j < delimiterBytes.length; j++) {
                if (delimiterBytes[j] != body[i + j]) {
                    matchedDelimiter = false;
                }
            }
            if (matchedDelimiter) {
                fileUpload.delimiterOffset = i + delimiterBytes.length;
                break;
            }
        }
        int contentLength = (fileUpload.delimiterOffset - headerSectionOffset - delimiterBytes.length - 2);
        fileUpload.data = new byte[contentLength];
        System.arraycopy(body, headerSectionOffset, fileUpload.data, 0, contentLength);

        return fileUpload;
    }
}